﻿using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

namespace Nomo.UnityCoreModule.UI
{
    /// <summary>
    /// 折线图。
    /// </summary>
    public sealed class LineChart : MaskableGraphic, IPointerClickHandler
    {
        public readonly struct LineChardPointModel
        {
            /// <summary>
            /// 下标。
            /// </summary>
            public readonly int Index;

            /// <summary>
            /// 是否被选中？
            /// </summary>
            public readonly bool Selected;

            /// <summary>
            /// 原始值。
            /// </summary>
            public readonly Vector2 Value;

            /// <summary>
            /// 相对于 <see cref="LineChart"/> 所在的 <see cref="Component.transform"/> 的 <see cref="Transform.localPosition"/> .
            /// </summary>
            public readonly Vector2 LocalPosition;

            public LineChardPointModel(int index, bool selected, Vector2 value, Vector2 localPosition)
            {
                Index         = index;
                Selected      = selected;
                Value         = value;
                LocalPosition = localPosition;
            }
        }

        [SerializeField]                    private RectOffset    padding                 = new RectOffset();
        [SerializeField]                    private Vector2       size                    = Vector2.one * 100;
        [SerializeField]                    private List<Vector2> points                  = new List<Vector2>();
        [Header("line")] [SerializeField]   private bool          drawLine                = true;
        [SerializeField] [Min(0F)]          private float         lineWidth               = 2F;
        [SerializeField]                    private Color         lineColor               = Color.white;
        [Header("column")] [SerializeField] private bool          drawColumn              = true;
        [SerializeField] [Min(0F)]          private float         columnWidth             = 2F;
        [SerializeField]                    private Color         unselectedColumnColor   = Color.black;
        [SerializeField]                    private Color         selectedColumnColor     = Color.gray;
        [Header("area")] [SerializeField]   private bool          drawArea                = true;
        [SerializeField]                    private Color         areaColor               = Color.white;
        [SerializeField]                    private bool          areaGradient            = false;
        [SerializeField]                    private Color         areaGradientTopColor    = Color.white;
        [SerializeField]                    private Color         areaGradientBottomColor = Color.white;

        private readonly List<Vector2> _localPoints = new List<Vector2>();

        private bool _selected;
        private int  _selectedIndex;
        private int? _legacySelectedIndex;

        /// <summary>
        /// <para>参数1：是否有点被选择；参数2：第几个点被选择了；参数3：被选择的点的源数据的Y值；参数4：被选择的点在本组件所在的变换下的局部坐标。</para>
        /// <para>如果参数1为<see langword="false"/>，那么剩余参数的值都为其各自类型的默认值。</para>
        /// </summary>
        public event Action<bool, int, float, Vector2> SelectedChanged;

        public LineChardPointModel[] GetLineChardPointModels()
        {
            if (points.Count < 1)
            {
                return null;
            }

            var count                = points.Count;
            var lineChardPointModels = new LineChardPointModel[count];
            for (var i = 0; i < count; i++)
            {
                lineChardPointModels[i] = new LineChardPointModel(i, _selected && _selectedIndex == i, points[i], _localPoints[i]);
            }
            return lineChardPointModels;
        }

        void IPointerClickHandler.OnPointerClick(PointerEventData eventData)
        {
            RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, eventData.pressPosition, eventData.pressEventCamera, out var localPoint);

            var nearest = 0;
            for (var i = 1; i < _localPoints.Count; i++)
            {
                var point = _localPoints[i];
                if (Mathf.Abs(point.x - localPoint.x) < Mathf.Abs(_localPoints[nearest].x - localPoint.x))
                {
                    nearest = i;
                }
            }

            if (_selected)
            {
                if (_selectedIndex == nearest)
                {
                    _selected = false;
                }
                else
                {
                    _selectedIndex = nearest;
                }
            }
            else
            {
                _selected      = true;
                _selectedIndex = nearest;
            }

            if (drawColumn)
            {
                SetVerticesDirty();
            }

            SelectedChanged?.Invoke(_selected, _selected ? _selectedIndex : default, _selected ? points[_selectedIndex].y : default, _selected ? _localPoints[_selectedIndex] : default);
        }

        /// <inheritdoc />
        protected override void OnPopulateMesh(VertexHelper vh)
        {
            if (size.x <= 0F || size.y <= 0F || points.Count < 2)
            {
                base.OnPopulateMesh(vh);
                return;
            }

            var rect   = GetPixelAdjustedRect();
            var width  = rect.width;
            var height = rect.height;
            if (width == 0F || height == 0F || width <= padding.horizontal || height <= padding.vertical)
            {
                base.OnPopulateMesh(vh);
                return;
            }

            vh.Clear();
            points.Sort(Comparison);
            _localPoints.Clear();
            for (var i = 0; i < points.Count; i++)
            {
                var point = points[i];
                _localPoints.Add(new Vector2(Mathf.LerpUnclamped(rect.xMin + padding.left, rect.xMax - padding.right, InverseLerpUnclamped(0F, size.x, point.x)), Mathf.LerpUnclamped(rect.yMin + padding.bottom, rect.yMax - padding.top, InverseLerpUnclamped(0F, size.y, point.y))));
            }
            DrawArea(vh, rect);
            DrawColumn(vh, rect);
            DrawLine(vh, rect);
        }

        private void DrawArea(VertexHelper vh, Rect pixelAdjustedRect)
        {
            if (!drawArea)
            {
                return;
            }

            var uvOffset          = new Vector2(0.5F, 0.5F);
            var minYOfLocalPoints = Mathf.Min(_localPoints.Min(e => e.y), pixelAdjustedRect.yMin + padding.bottom);
            var maxYOfLocalPoints = _localPoints.Max(e => e.y);

            for (var i = 0; i < _localPoints.Count - 1; i++)
            {
                var p1 = _localPoints[i];
                var p2 = _localPoints[i + 1];

                var uiVertices = new UIVertex[4];
                for (var j = 0; j < 4; j++)
                {
                    uiVertices[j] = UIVertex.simpleVert;
                }

                float x;
                float y;

                x = p1.x;
                y = p1.y;
                if (!areaGradient)
                {
                    uiVertices[1].color = areaColor * color;
                }
                else
                {
                    uiVertices[1].color = Color.Lerp(areaGradientBottomColor, areaGradientTopColor, InverseLerpUnclamped(minYOfLocalPoints, maxYOfLocalPoints, y)) * color;
                }
                uiVertices[1].position = new Vector2(x, y);
                uiVertices[1].uv0      = new Vector2(x / pixelAdjustedRect.width, y / pixelAdjustedRect.height) + uvOffset;

                x = p1.x;
                y = pixelAdjustedRect.yMin + padding.bottom;
                if (!areaGradient)
                {
                    uiVertices[0].color = areaColor * color;
                }
                else
                {
                    uiVertices[0].color = areaGradientBottomColor * color;
                }
                uiVertices[0].position = new Vector2(x, y);
                uiVertices[0].uv0      = new Vector2(x / pixelAdjustedRect.width, y / pixelAdjustedRect.height) + uvOffset;

                x = p2.x;
                y = pixelAdjustedRect.yMin + padding.bottom;
                if (!areaGradient)
                {
                    uiVertices[3].color = areaColor * color;
                }
                else
                {
                    uiVertices[3].color = areaGradientBottomColor * color;
                }
                uiVertices[3].position = new Vector2(x, y);
                uiVertices[3].uv0      = new Vector2(x / pixelAdjustedRect.width, y / pixelAdjustedRect.height) + uvOffset;

                x = p2.x;
                y = p2.y;
                if (!areaGradient)
                {
                    uiVertices[2].color = areaColor * color;
                }
                else
                {
                    uiVertices[2].color = Color.Lerp(areaGradientBottomColor, areaGradientTopColor, InverseLerpUnclamped(minYOfLocalPoints, maxYOfLocalPoints, y)) * color;
                }
                uiVertices[2].position = new Vector2(x, y);
                uiVertices[2].uv0      = new Vector2(x / pixelAdjustedRect.width, y / pixelAdjustedRect.height) + uvOffset;

                vh.AddUIVertexQuad(uiVertices);
            }
        }

        private void DrawColumn(VertexHelper vh, Rect pixelAdjustedRect)
        {
            if (!drawColumn)
            {
                return;
            }

            for (var i = 0; i < _localPoints.Count; i++)
            {
                var point = _localPoints[i];

                GraphicHelper.AddLine(this, vh, new Vector2(point.x, pixelAdjustedRect.yMin + padding.bottom), new Vector2(point.x, pixelAdjustedRect.yMax - padding.top), columnWidth, _selected && _selectedIndex == i ? selectedColumnColor : unselectedColumnColor);
            }
        }

        private void DrawLine(VertexHelper vh, Rect pixelAdjustedRect)
        {
            if (!drawLine)
            {
                return;
            }

            if (lineWidth <= 0F)
            {
                return;
            }

            for (var i = 0; i < _localPoints.Count - 1; i++)
            {
                GraphicHelper.AddLine(this, vh, _localPoints[i], _localPoints[i + 1], lineWidth, lineColor);
            }
        }

        private static int Comparison(Vector2 x, Vector2 y)
        {
            var x1 = x.x;
            var x2 = y.x;
            if (float.IsNaN(x1))
            {
                if (float.IsNaN(x2))
                {
                    return 0;
                }

                return -1;
            }

            if (float.IsNaN(x2))
            {
                return -1;
            }

            return Math.Sign(x1 - x2);
        }

        public static float InverseLerpUnclamped(float a, float b, float value)
        {
            return a != b ? (value - a) / (b - a) : 0F;
        }
    }
}
